package com.gitee.aspect;

import com.gitee.annotation.DefaultValue;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

@Aspect
@Component
@Slf4j
public class DefaultValueAspect {
    @Pointcut("execution(* com.gitee.mapper.*.*(..)) && @annotation(com.gitee.annotation.DefaultValue)")
    public void defaultValuePointCut() {
    }

    @Before("defaultValuePointCut()")
    public void defaultValue(JoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
        log.info("默认值填充");
        // 获得正在被调用的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获得方法上的DefaultValue注解
        DefaultValue annotation = signature.getMethod().getAnnotation(DefaultValue.class);
        // 得到所有排除的集合
        List<String> excludeKey = List.of(annotation.excludeKey());

        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }

        // 默认要赋值的参数是第一个参数
        Object arg = args[0];
        // 通过arg获取类对象
        Class<?> aClass = arg.getClass();

        // 通过类对象查找所有public方法
        Method[] declaredMethods = aClass.getMethods();

        // 存储成员名的
        List<String> fieldNames = new LinkedList<>();

        // 统一执行get方法, 获得空值的属性对应的字段
        for (Method declaredMethod : declaredMethods) {
            String methodName = declaredMethod.getName();
            // 只对get方法进行操作
            if (methodName.startsWith("get")) {
                String fieldName = getMethod2FiledName(methodName);
                // 若不包含要排除的成员, 才将其插入List
                if (!contains(excludeKey, fieldName)) {
                    // 执行get方法, 读取属性值
                    Object result = declaredMethod.invoke(arg);
                    // 如果返回的值是空的, 就把fieldName存到List中, 之后统一执行set方法, 赋默认值
                    // 为什么不直接在这执行set方法, 为了程序的可读性, 避免太多层嵌套
                    if (result == null) {
                        // 将成员名插入List
                        fieldNames.add(fieldName);
                    }
                }
            }
        }

        // 统一执行set方法
        for (String fieldName : fieldNames) {
            // 通过字段名获得字段
            Field declaredField = aClass.getDeclaredField(fieldName);
            String type = declaredField.getType().getSimpleName();

            // 通过filedName拼接出setMethodName
            String setMethodName = "set" + firstCharUpper(fieldName);

            switch (type) {
                // 如果是Integer类型, 则默认值为0
                // 通过方法名, 反射方法, 类型就是本次需要修改变量的类型
                case "Byte"      -> aClass.getDeclaredMethod(setMethodName, Byte.class).invoke(arg, (byte) 0);
                case "Long"      -> aClass.getDeclaredMethod(setMethodName, Long.class).invoke(arg, 0L);
                case "Float"     -> aClass.getDeclaredMethod(setMethodName, Float.class).invoke(arg, 0.0f);
                case "Short"     -> aClass.getDeclaredMethod(setMethodName, Short.class).invoke(arg, (short) 0);
                case "Double"    -> aClass.getDeclaredMethod(setMethodName, Double.class).invoke(arg, 0.0);
                case "String"    -> aClass.getDeclaredMethod(setMethodName, String.class).invoke(arg, "");
                case "Integer"   -> aClass.getDeclaredMethod(setMethodName, Integer.class).invoke(arg, 0);
                case "Boolean"   -> aClass.getDeclaredMethod(setMethodName, Boolean.class).invoke(arg, false);
                case "Character" -> aClass.getDeclaredMethod(setMethodName, Character.class).invoke(arg, '0');
            }
        }
    }

    /**
     * get方法名转成员名
     * @param methodName get方法名
     * @return 成员名
     */
    public String getMethod2FiledName(String methodName) {
        return methodName.toLowerCase(Locale.ROOT).charAt(3) + methodName.substring(4);
    }

    /**
     * 首字母大写
     * @param str 字符串
     * @return 首字母大写后的字符串
     */
    public String firstCharUpper(String str) {
        return str.toUpperCase(Locale.ROOT).charAt(0) + str.substring(1);
    }

    /**
     * 集合中是否包含目标
     * @param source 集合
     * @param target 目标
     * @return true表示包含
     */
    public boolean contains(List<String> source, String target) {
        for (String s : source)
            if (s.equals(target)) return true;
        return false;
    }
}
